<?php
//session_start();
/**
 * ExplorerLite 3.3.0
 * 在线php/ini/conf/sh等脚本文件编辑器, 不依赖ftp和服务器帐号(单页绿色文件,方便部署)
 * @version 1.0 <2015-5-11> SoChishun <14507247@qq.com> Added.
 * @version 2.0 <2015-7-24> SoChishun 
 *      1.重命名为ExplorerLite.php
 *      2.改进若干外观样式
 *      3.新增登录验证模块
 *      4.新增删除功能
 * @version 3.0 <2015-10-7> SoChishun
 *      1.新增在线压缩和解压功能
 *      2.新增chomd权限设置功能
 *      3.新增rename重命名功能
 *      4.新增新建文件和目录功能
 *      5.类SuFileEditor重构为ExplorerLite,类方法改为静态方法
 *      6.新增主配置文件功能
 *      7.重构页面逻辑，改为脚本混合代码，便于阅读
 *      8.基于绝对路径操作改为基于网站根目录的相对路径操作
 *  @version 3.1 <2016-9-13> SoChishun
 *      1. 新增文件上传功能
 * @version 3.2 <2017-8-23> SoChishun
 *      1. 修正无法查看脚本文件的问题
 *      2. 对html输出增加htmlspecialchars过滤功能
 * @since 3.3 <2017-8-25> SoChishun 由SuExplorer.php重构为ExplorerLite.php
 *      1. 增加树形目录侧边栏，可以更直观了解目录层级，快速找到深层子目录
 *      2. 增加多种访问验证模式
 * @version 4.0 2018-1-13 SoChishun
 *      1. 重构登录模块,为适应负载均衡多台服务器分布式使用场景,登录会话保持由单机的session存储改为url参数token,支持续期功能，避免频繁超时重新登录的繁琐操作
 *      2. 引入ptshead.php头文件,简化代码,提高代码复用
 *      3. 主类SuExplorer重命名为ExplorerLite, 增加路径的黑白名单功能
 *      4. 增加文件内容统计和属性信息显示
 */
// 版本号 [2015-10-7] Added.
define('VERSION', '4.0.0');
// 程序名称
define('APPNAME', 'ExplorerLite');
// 根目录
define('ROOTPATH', '../logs/');

// 引入头文件
include './pts_head.php';
// #JWTLite配置
// 页面访问验证配置
JWTLite::check_login();
list($loginid, $token, $loginexp) = array_values(JWTLite::$LOGIN_RESULT);

// 主要配置
$config = array(
    /* 忽略的路径 */
    'skip' => array('../.git/'),
    /* 允许上传的文件格式 */
    'upload_ext' => array('zip', 'txt', 'jpg', 'jpeg', 'png', 'gif', 'bmp'),
);

// 获取输入参数
$action = input('action');
$view = input('view');
$path = input('path', ROOTPATH); // 路径
$dirpath = input('dirpath'); // 目录路径
switch ($action) {
    case 'delete': // 删除路径(文件或目录)
    case 'clear':
        $doResult = ExplorerLite::doDeletePath($path, 'delete' == $action);
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . pathinfo($path, PATHINFO_DIRNAME), 1, '恭喜,操作成功!');
        }
        break;
    case 'savefile': // 保存文件
    case 'save_newfile': // 新建文件
        $doResult = ExplorerLite::doSaveFile();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . $path, 1, '恭喜,操作成功!');
        }
        break;
    case 'save_newdir': // 新建目录
        $doResult = ExplorerLite::doMkdir();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . $path, 1, '恭喜,操作成功!');
        }
        break;
    case 'upload_file': // 上传文件
        $doResult = ExplorerLite::doUpload();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . $path, 1, '恭喜,文件上传成功!');
        }
        break;
    case 'rename_path': // 重命名路径(文件或目录)
        $doResult = ExplorerLite::doRename();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . pathinfo($path, PATHINFO_DIRNAME), 1, '恭喜,操作成功!');
        }
        break;
    case 'chmod_path': // 修改权限(文件或目录)
        $doResult = ExplorerLite::doChmod();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . $path, 1, '恭喜,操作成功!');
        }
        break;
    case 'zip': // 压缩
        $doResult = ExplorerLite::doZip();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . $path, 1, '恭喜,操作成功!');
        }
        break;
    case 'unzip': // 解压缩
        $doResult = ExplorerLite::doUnzip();
        if (!$doResult['status']) {
            redirect('?token=' . $token . '&path=' . $path, 1, $doResult['info']);
        } else {
            redirect('?token=' . $token . '&path=' . $path, 1, '恭喜,操作成功!');
        }
        break;
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title><?php echo APPNAME . ' - ' . VERSION ?></title>
        <style type="text/css">
            body {font-size:12px; color:#333;}
            a{text-decoration: none;}
            textarea{font-size:12px;line-height:18px; padding:5px;}
            th{font-weight: normal;}
            aside{max-height:780px;overflow:auto;font-size:13px}
            main{margin-left:10px;}
            li{list-style:none;margin-left:-40px;}
            li strong {font-weight: normal;}
            li .active { font-weight: bold; color:#F00;}
            li .active a{ color:#F00;}
            .userbar a:before{content: '['}
            .userbar a:after{content: ']'}
            .dir-contents{width:90%; display:table;}
            .dir-contents a{ margin-right:20px;line-height:21px;text-decoration:none;float:left;}
            .blue{color:#0000DB}
            .lightblue{color:#1bd1a5}
            .purple{color:#9900ff}
            .green{color:#009900}
            .red{color:#F00}
            .grey {color:#999;}
            .nav { line-height: 18px;}
            .nav a { color:#333;}
            .nav a:before { content: ' » ' }
            .nav a:first-child:before {content: ''}
            .nav div { color:#CCC; border-bottom:solid 1px #CCC; margin-bottom:5px;}
            th { word-break: keep-all;}
        </style>
    </head>
    <body>
        <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
        <?php
        echo '<h1>', APPNAME, ' ';
        echo '<a href="?token=', $token, '" title="新开一个页面" target="_blank" style="font-size:12px;">[新窗口]</a>';
        echo '<a href="#" title="刷新当前页面" style="font-size:12px;" onclick="location.reload()">[刷新]</a>';
        echo '</h1>';
// 用户登录信息
        if (isset($loginid)) {
            echo '<span title="登录过期时间：', $loginexp, ' (超时前进行跳转操作能让登录会话自动延期,避免重登录操作~)">', $loginid, '</span>';
            echo ' [<a href="?action=logout" onclick="return confirm(\'您确定要注销登录吗?\');">注销登录</a>]';
        }
        echo ' 客户端IP:', $_SERVER['REMOTE_ADDR'], ' <span style="display:none">服务器IP:' . JWTLite::get_host_ip() . '</span> <a href="#" id="lnk-toggle-ipinfo">[...]</a>';
        ?>
        <!-- 用户信息栏 -->
        <div class="userbar">
            <?php
            $parentpath = is_file($path) ? pathinfo($path, PATHINFO_DIRNAME) : $path;
            ?>
            <a href="?token=<?php echo $token ?>&path=<?php echo ROOTPATH ?>">根目录</a>
            <a href="?token=<?php echo $token ?>&view=newfile&path=<?php echo $parentpath ?>">新建文件</a>
            <a href="?token=<?php echo $token ?>&view=upload&path=<?php echo $parentpath ?>">上传文件</a>
            <a href="?token=<?php echo $token ?>&view=newdir&path=<?php echo $parentpath ?>">新建目录</a>
            <a href="?token=<?php echo $token ?>&view=zip&path=<?php echo $parentpath ?>">打包目录</a>
            <a href="?token=<?php echo $token ?>&view=unzip&path=<?php echo $parentpath ?>">解压目录</a>
        </div>
        <!-- /用户信息栏 -->
        <!-- 表格分栏布局-->
        <table border="0" style="width:100%">
            <tr>
                <td valign="top" style="width:210px">
                    <aside>
                        <ul>
                            <?php
                            $dirpath = '';
                            $files = FileUtil::scandirReturn(ROOTPATH, 0, false, $config['skip']);
                            FileUtil::showFiles($token, $files, 0, $path, $dirpath, array(
                                'btn_refresh_visible' => false, // 是否显示目录刷新按钮
                                'btn_open_visible' => true, // 是否显示目录新窗口打开按钮
                                'btn_clear_visible' => true, // 是否显示目录清空按钮
                                'btn_rmdir_visible' => true, // 是否显示目录删除按钮
                                'btn_unlink_visible' => true, // 是否显示文件删除按钮
                                'btn_download_visible' => true, // 是否显示文件下载按钮
                            ));
                            ?>
                        </ul>
                    </aside>
                </td>
                <td valign="top">
                    <main>
                        <!-- 路径栏 -->
                        <div>
                            <form method="get" action="#" id="frm-path">
                                <input type="text" name="path" value="<?php echo $path ?>" style="width:60%;color:#333;padding:0px 2px;" required="required" />
                                <input type="hidden" id="action" name="action" value="" />
                                <input type="hidden" id="do" name="do" value="" />
                                <input type="hidden" id="token" name="token" value="<?php echo $token ?>" />
                                <button type="submit">转到</button>
                                <button type="button" onclick="return del_cofirm('frm-path', '删除');">删除</button>
                                <button type="button" data-url="<?php echo '?token=', $token, '&path=', $path, '&view=rename' ?>" onclick="location.href = this.getAttribute('data-url')">重命名</button>
                                <button type="button" data-url="<?php echo '?token=', $token, '&path=', $path, '&view=chmod' ?>" onclick="location.href = this.getAttribute('data-url')">权限</button>
                            </form>
                        </div>
                        <!-- /路径栏 -->
                        <div><?php ExplorerLite::index($token, $view, $path) ?></div>
                    </main>
                </td>
            </tr>
        </table>
        <!-- /表格分栏布局-->
        <!-- 脚本区 -->
        <script type="text/javascript">
            /**
             * 删除确认
             * @param {type} form_id
             * @param {type} act_name
             * @returns {Boolean}
             * @version 1.0 <2015-10-7> SoChishun Added.
             */
            function del_cofirm(form_id, act_name) {
                if (!confirm('您确定要' + act_name + '吗?')) {
                    return false;
                }
                var i = 0;
                function confirmx() {
                    i++;
                    return confirm(i + '.重要的操作要重复问三遍,您确定要' + act_name + '吗?');
                }
                while (i < 3) {
                    if (!confirmx()) {
                        return false;
                    }
                }
                document.getElementById("action").value = "delete";
                document.getElementById("do").value = "yes";
                document.getElementById(form_id).submit();
            }
            // 隐藏空的switch按钮 2017-8-25
            /*
             $(".btn_switch").each(function () {
             if ($(this).next().children().size() < 1) {
             $(this).hide();
             }
             })*/
            // 自动补充输入框的title属性 2018-1-13
            $('input, textarea').each(function () {
                var $input = $(this);
                if ($input.attr('placeholder') && !$input.attr('title')) {
                    $input.attr('title', $input.attr('placeholder'));
                }
            })
            $(".btn_switch").click(function () {
                var $this = $(this);
                if ($this.text() == "[-]") {
                    $this.text("[+]").attr("title", "展开");
                    $this.parent().children("ul").hide();
                } else {
                    $this.text("[-]").attr("title", "收起");
                    $this.parent().children("ul").show();
                }
            });
        </script>
        <!-- /脚本区 -->
    </body>
</html>
<?php

/**
 * 页面主类
 * @version 1.0 <2015-5-11> SoChishun <14507247@qq.com> Added.
 * @version 3.0 <2015-10-7> SoChishun 重构.
 * @version 4.0 2018-1-13 SoChishun 由 SuExplore 重命名为 ExploreLite
 */
class ExplorerLite {

    /**
     * 版本号
     * @var string
     * @version 1.0 <2015-5-11> SoChishun <14507247@qq.com> Added
     * @version 3.0 <2015-10-7> SoChishun 重构
     * @version 4.0 2018-1-13 SoChishun 由 SuExplore 重命名为 ExploreLite
     */
    CONST VERSION = '4.0.0';

    /**
     * 显示网站目录的项目内容
     * @version 1.0 <2015-5-11> SoChishun Added.
     */
    static function index($token, $view, $path) {
        // 面包屑导航
        self::showBreadcrumb($token, $path);
        // 视图显示
        switch ($view) {
            case 'newfile':
                self::showCreateFileForm($token, $path);
                break;
            case 'newdir':
                self::showCreateDirForm($token, $path);
                break;
            case 'upload':
                self::showUploadForm($token, $path);
                break;
            case 'rename':
                self::showRenameForm($token, $path);
                break;
            case 'chmod':
                self::showChmodForm($token, $path);
                break;
            case 'zip':
                self::showZipForm($token, $path);
                break;
            case 'unzip':
                self::showUnzipForm($token, $path);
                break;
            default:
                // 列出文件
                if (is_dir($path)) {
                    self::showFileList($token, $path);
                } else if (is_file($path)) {
                    self::showEditFileForm($token, $path);
                } else {
                    echo '<strong class="red">', $path, ' 路径不存在!</strong>';
                }
                break;
        }
    }

    /**
     * 删除非空目录里面所有文件和子目录
     * @param string $dir
     * @param boolean $delSelf 是否删除自身目录(删除还是清空)
     * @return array
     * @version 1.0 <2015-10-7> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_rmdir 重命名为 doRmdir
     */
    static function doRmdir($dir, $delSelf = true) {
        //先删除目录下的文件：
        $dh = opendir($dir);
        while ($file = readdir($dh)) {
            if ($file != "." && $file != "..") {
                $fullpath = $dir . "/" . $file;
                if (is_dir($fullpath)) {
                    self::doRmdir($fullpath, true);
                } else {
                    unlink($fullpath);
                }
            }
        }
        closedir($dh);
        //删除当前文件夹：
        if (!$delSelf || rmdir($dir)) {
            return infomsg(true, '删除成功!');
        } else {
            return infomsg(false, '删除失败!');
        }
    }

    /**
     * 删除路径(文件或目录)
     * @param string $path 路径
     * @return array
     * @version 1.0 <2015-10-7> SoChishun Added.
     * @version 2.0 <2015-10-8> SoChishun 将delete_file和delete_dir合并到delete_path
     * @version 3.0 2018-1-13 SoChishun 由 act_delete_path 重命名为 doDeletePath
     */
    static function doDeletePath($path, $delSelf = true) {
        if ('yes' != input('do')) {
            // 为防止黑客破坏,删除操作需要手动增加参数do=yes
            return infomsg(false, '非法操作!');
        }
        if (ROOTPATH == $path) {
            return infomsg(false, '根目录无法删除!');
        }
        if (is_file($path)) {
            $result = @unlink($path);
            return infomsg($result, $result ? '文件删除成功!' : '文件删除失败!');
        }
        if (is_dir($path)) {
            $result = @rmdir($path);
            return infomsg($result, $result ? '目录删除成功!' : '目录删除失败!(非空目录或权限不足)!');
        }
        return infomsg(false, '不是有效文件或目录!');
    }

    /**
     * 保存(新)文件
     * @return array
     * @version 1.0 <2015-10-7> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_save_file 重命名为 doSaveFile
     */
    static function doSaveFile() {
        $filename = input('filename');
        if (!$filename || !strpos($filename, '.')) {
            return infomsg(false, '文件扩展名无效!');
        }
        $content = input('content');
        $path = input('path');
        if (is_file($path)) {
            $path = pathinfo($path, PATHINFO_DIRNAME);
        }
        $newpath = $path . DIRECTORY_SEPARATOR . $filename;
        try {
            $result = file_put_contents($newpath, $content);
            return infomsg(false !== $result, false !== $result ? '操作成功!' : '操作失败!');
        } catch (Exception $ex) {
            return infomsg(false, $ex->getMessage());
        }
    }

    /**
     * 保存新目录
     * @return array
     * @version 1.0 <2015-10-8> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_save_newdir 重命名为 doMkdir
     */
    static function doMkdir() {
        $filename = input('filename');
        if (!$filename) {
            return infomsg(false, '目录名称无效!');
        }
        $path = input('path');
        $mode = input('mode');
        $newpath = str_replace('//', '/', $path . '/') . $filename;
        if (strlen($newpath) < strlen(ROOTPATH)) {
            return infomsg(false, '路径越权!');
        }
        try {
            $result = mkdir($newpath, $mode);
            return infomsg($result, $result ? '操作成功!' : '操作失败!');
        } catch (Exception $ex) {
            return infomsg(false, $ex->getMessage());
        }
    }

    /**
     * 上传文件
     * @return array
     * @version 1.0 <2016-9-13> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_upload_file 重命名为 doUpload
     */
    static function doUpload() {
        if ($_FILES["file"]["error"] > 0) {
            return infomsg(false, $_FILES["file"]["error"]);
        }

        $path = input('path');
        $filename = $_FILES["file"]["name"];
        if (file_exists($path . $filename)) {
            return infomsg(false, $filename . ' 文件已存在!!');
        }
        $result = move_uploaded_file($_FILES["file"]["tmp_name"], $path . $filename);
        return infomsg($result, $result ? '操作成功!' : '操作失败!');
    }

    /**
     * 重命名路径操作
     * @return array
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_rename_path 重命名为 doRename
     */
    static function doRename() {
        $filename2 = input('filename2');
        if (!$filename2) {
            return infomsg(false, '新名称未填写!');
        }
        $path = input('path');
        if (is_file($path) && !strpos($filename2, '.')) {
            return infomsg(false, '文件扩展名无效!');
        }
        try {
            $newname = pathinfo($path, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . $filename2;
            $result = @rename($path, $newname);
            return infomsg($result, $result ? '操作成功!' : '操作失败!');
        } catch (Exception $ex) {
            return infomsg(false, $ex->getMessage());
        }
    }

    /**
     * 编辑权限操作
     * @return array
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_chmod_path 重命名为 doChmod
     */
    static function doChmod() {
        $mode = input('mode');
        $path = input('path');
        if (!$mode) {
            return infomsg(false, '权限模式无效!');
        }
        try {
            $result = chmod($path, $mode);
            return infomsg($result, $result ? '操作成功!' : '操作失败!');
        } catch (Exception $ex) {
            return infomsg(false, $ex->getMessage());
        }
    }

    /**
     * 压缩操作
     * @return array
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_zip 重命名为 doZip
     */
    static function doZip() {
        $filename = input('filename');
        $content = input('content');
        $rootpath = input('rootpath');
        if (!$filename || false === strpos($filename, '.')) {
            return infomsg(false, '压缩文件名无效! (' . $filename . ')');
        }
        if (!$content) {
            return infomsg(false, '压缩内容无效!');
        }
        $include = array();
        $exclude = array();
        $paths = explode(PHP_EOL, $content);
        foreach ($paths as $path) {
            if (0 === strpos($path, 'exclude ')) {
                $exclude[] = trim(substr($path, 8));
            } else {
                $include[] = $path;
            }
        }
        if (!$include) {
            return infomsg(false, '压缩内容无效!');
        }
        $zip = new ZipHelper();
        return $zip->zip($filename, $include, $exclude, $rootpath);
    }

    /**
     * 解压缩文件
     * @return array
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由 act_unzip 重命名为 doUnzip
     */
    static function doUnzip() {
        $root = input('root');
        $filename = input('filename');
        if (!$filename) {
            return infomsg(false, '压缩文件路径无效!');
        }
        if (!$root) {
            return infomsg(false, '解压缩路径无效!');
        }
        $zip = new ZipHelper();
        return $zip->unzip($filename, $root);
    }

    /**
     * 目录内容视图
     * @param type $path
     * @version 1.0 <2015-5-11> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 优化安全问题
     */
    static function showFileList($token, $path) {
        if (strlen($path) < strlen(ROOTPATH)) {
            $path = ROOTPATH; // 防止通过../路径越权访问
        }
        $files = FileUtil::getFileInfoList($path);
        echo '<div class="dir-contents">';
        echo '<div style="color:#666">count:', count($files), ', modified time:', date('Y-m-d H:i:s', filectime($path)), ', readable:', is_readable($path), ', writeable:', is_writable($path), ', executable:', is_executable($path), '</div><div style="color:#555; border:solid 1px #CCC; padding:3px 5px; margin:5px 0;">蓝色表示目录,绿色表示可执行文件,浅蓝色表示链接文件,红色表示压缩文件,紫色表示图形文件,灰色表示其他文件</div>';
        foreach ($files as $file) {
            if ($file['is_dir']) {
                echo '<a href="?token=' . $token . '&path=' . $file['filepath'] . '" class="blue"><strong>' . $file['name'] . '</strong></a>';
            } else {
                $class = '';
                if ($file['is_link']) {
                    $class = 'lightblue';
                } else if ($file['is_executable']) {
                    $class = 'green';
                } else {
                    switch ($file['filetype']) {
                        case 'zip':
                            $class = 'red';
                            break;
                        case 'image':
                            $class = 'purple';
                            break;
                        default:
                            $class = 'grey';
                            break;
                    }
                }
                echo '<a href="?token=' . $token . '&path=' . $file['filepath'] . '" class="' . $class . '">' . $file['name'] . '</a>';
            }
        }
        echo '<div style="clear:both"></div></div>';
    }

    /**
     * 文件内容编辑视图
     * @param type $path
     * @version 1.0 <2015-5-11> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_edit_file重命名为showEditFileForm
     */
    static function showEditFileForm($token, $path) {
        if (!is_file($path)) {
            return;
        }
        $category = get_exten_catetory(pathinfo($path, PATHINFO_EXTENSION));
        switch ($category) {
            case 'doc':
            case 'script':
                $btns = '<button type="submit">保存</button><button type="reset">重置</button>';
                if (!is_writable($path)) {
                    echo '<div style="color:#F00">文件不可写</div>';
                    $btns = '';
                }
                echo '<div>
                    <form method="post">
                        ', $btns, '<div style="margin-top:3px">
                        <div style="color:#666">size:', FileUtil::formatBytes(filesize($path)), ', modified time:', date('Y-m-d H:i:s', filectime($path)), ', readable:', is_readable($path), ', writeable:', is_writable($path), ', executable:', is_executable($path), '</div>
                        <textarea name="content" cols="60" rows="36" style="width:90%">' . htmlspecialchars(file_get_contents($path)) . '</textarea>
                        </div>', $btns, '
                        <input type="hidden" name="action" value="savefile" />
                        <input type="hidden" name="filename" value="' . basename($path) . '" />
                        <input type="hidden" name="path" value="' . $path . '" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
                break;
            case 'image':
                echo '<img src="', $path, '" alt="" style="max-width:90%;max-height:640px;" /><br />';
                echo basename($path);
                echo ' <a href="', $path, '" target="_blank">[原图]</a>';
                break;
            default:
                echo basename($path);
                echo ' <a href="', $path, '" target="_blank">[下载]</a>';
                break;
        }
    }

    /**
     * 新增文件视图
     * @param string $path 路径
     * @version 1.0 <2015-10-8> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_create_file重命名为showCreateFileForm
     */
    static function showCreateFileForm($token, $path) {
        echo '<div>
                    <form method="post">
                        <table>
                            <tr><th>文件名：</th><td><input type="text" name="filename" required="required" placeholder="如：newfile.txt" /></td></tr>
                            <tr><th valign="top">内容：</th><td><textarea name="content" cols="90" rows="12"></textarea></td></tr>
                            <tr><td>&nbsp;</td><td><button type="submit">创建文件</button><button type="reset">重置</button></td></tr>
                        </table>
                        <input type="hidden" name="action" value="save_newfile" />
                        <input type="hidden" name="path" value="' . $path . '" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 新增目录视图
     * @param string $path 路径
     * @version 1.0 <2015-10-8> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_create_dir重命名为showCreateDirForm
     */
    static function showCreateDirForm($token, $path) {
        echo '<div>
                    <form method="post">
                        目录名：<input type="text" name="filename" required="required" /><br />
                        <div style="margin:5px 0">权限模式：<input type="text" name="mode" required="required" value="0777" /></div>
                        <button type="submit">创建目录</button> <button type="reset">重置</button>
                        <input type="hidden" name="action" value="save_newdir" />
                        <input type="hidden" name="path" value="' . $path . '" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 上传文件视图
     * @param string $token
     * @param string $path 路径
     * @version 1.0 <2015-10-8> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_upload_file重命名为showUploadForm
     */
    static function showUploadForm($token, $path) {
        echo '<div>
                    <form method="post" enctype="multipart/form-data">
                        <table>
                            <tr><th>选择文件：</th><td><input type="file" name="file" required="required" /></td></tr>
                            <tr><td>&nbsp;</td><td><button type="submit">立即上传</button> <button type="reset">重置</button></td></tr>
                        </table>
                        <input type="hidden" name="action" value="upload_file" />
                        <input type="hidden" name="path" value="' . $path . '" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 重命名文件视图
     * @param string $path 路径
     * @version 1.0 <2015-10-8> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_rename_path重命名为showRenameForm
     */
    static function showRenameForm($token, $path) {
        echo '<div>
                    <form method="post">
                        原名称：<input type="hidden" name="filename" value="' . basename($path) . '" />' . basename($path) . '<br />
                        <div style="margin:5px 0">新名称：<input type="text" name="filename2" required="required" /></div>
                        <button type="submit">重命名</button> <button type="reset">重置</button>
                        <input type="hidden" name="action" value="rename_path" />
                        <input type="hidden" name="path" value="' . $path . '" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 编辑权限视图
     * @param string $path 路径
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_chmod_path重命名为showChmodForm
     */
    static function showChmodForm($token, $path) {
        echo '<div>
                    <form method="post">
                        名称：' . basename($path) . '<br />
                        <div style="margin:5px 0">权限模式：<input type="text" name="mode" required="required" value="0777" /></div>
                        <button type="submit">设置</button> <button type="reset">重置</button>
                        <input type="hidden" name="action" value="chmod_path" />
                        <input type="hidden" name="path" value="' . $path . '" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 压缩文件视图
     * @param string $token
     * @param string $path
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_zip重命名为showZipForm
     */
    static function showZipForm($token, $path) {
        $name = basename($path);
        if (is_file($path)) {
            $path = pathinfo($path, PATHINFO_DIRNAME);
        }
        $path = $path . '/';
        //$path = path_rtoa($path);
        echo '<div>
                    <form method="post">
                        <table>
                            <tr><th>压缩文件名：</th><td><input type="text" name="filename" required="required" size="89" placeholder="绝对路径,如：C:\public\www\website1\newfile.zip 或相对路径, 如：', ROOTPATH, '/zipfile.zip" value="' . $path . $name . '.zip" /></td></tr>
                            <tr><th valign="top">压缩内容：</th><td><textarea name="content" cols="90" rows="12" placeholder="每个路径一行,可以是相对路径或绝对路径,区别在于打包后压缩包内容的路径层次">', $path, '</textarea></td></tr>
                            <tr><th>去除根路径：</th><td><input type="text" name="rootpath" size="89" placeholder="绝对路径,如：C:\public\www\website1 或相对路径, 如：', ROOTPATH, '/" /></td></tr>
                            <tr><td>&nbsp;</td><td class="red">
                            注意，所有路径都必需是绝对路径!<br />
                            包含路径示例：C:\public\www\website1\app<br />
                            排除路径示例: exclude C:\public\www\website1\app\runtime<br />
                            如果填写跟路径地址,则压缩内容会自动去除跟路径信息(解压缩的时候,可以解压缩到任意目录下),如果根路径为空则保留根路径信息(解压缩的时候,无法解压缩到任意目录下,系统会自动创建和压缩前路径一样的目录)
                            </td></tr>
                            <tr><td>&nbsp;</td><td><button type="submit">创建压缩文件</button><button type="reset">重置</button></td></tr>
                        </table>                        
                        <input type="hidden" name="action" value="zip" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 解压缩文件视图
     * @version 1.0 <2015-10-9> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 由view_unzip重命名为showUnzipForm
     */
    static function showUnzipForm($token, $path) {
        $name = basename($path);
        if (is_file($path)) {
            $path = pathinfo($path, PATHINFO_DIRNAME);
        }
        $path = path_rtoa($path);
        echo '<div>
                    <form method="post">
                        <table>
                            <tr><th>压缩文件名：</th><td><input type="text" name="filename" required="required" size="89" placeholder="绝对路径,如：C:\public\www\website1\newfile.zip" value="' . $path . $name . '.zip" /></td></tr>
                            <tr><th>解压缩路径：</th><td><input type="text" name="root" required="required" size="89" placeholder="绝对路径,如：C:\public\www\website1" value="', $path, '" /></td></tr>
                            <tr><td>&nbsp;</td><td><button type="submit">解压缩文件</button><button type="reset">重置</button></td></tr>
                        </table>
                        <input type="hidden" name="action" value="unzip" />
                        <input type="hidden" name="token" value="' . $token . '" />
                    </form>
                </div>';
    }

    /**
     * 路径转为导航
     * @param string $path
     * @version 1.0 <2015-5-11> SoChishun Added.
     * @version 2.0 2018-1-13 SoChishun 重构,提高安全性
     */
    static function showBreadcrumb($token, $path) {
        echo '<div class="nav"><a href="?token=' . $token . '&path=', ROOTPATH, '">/</a>';
        $asubpath = explode('/', $path);
        if ($asubpath) {
            $str = '';
            $n = count($asubpath);
            $n2 = $n - 1;
            $rootlen = strlen(ROOTPATH);
            for ($i = 0; $i < $n; $i++) {
                $sub = $asubpath[$i];
                $str .= $sub . ($i < $n2 ? '/' : '');
                if (substr($str, 0, $rootlen) != ROOTPATH) {
                    continue; // 防止显示../路径而把ROOTPATH以上的目录显示出来,造成安全问题
                }
                echo '<a href="?token=' . $token . '&path=', $str, '">', $sub, '</a>';
            }
        }
        echo '<div>', $path, ' <a href="###" title="文件列表颜色含义：蓝色表示目录, 绿色表示可执行文件, 浅蓝色表示链接文件, 红色表示压缩文件, 紫色表示图形文件, 灰色表示其他文件">(?)</a></div>';
        echo '</div>';
    }

}

/**
 * 压缩类
 * @version 1.0 <2015-10-9> SoChishun Added.
 */
class ZipHelper {

    /**
     * 解压缩之
     * @param string $filename
     * @param string $root
     * @return array
     * @version 1.0 <2015-10-9> SoChishun Added.
     */
    function unzip($filename, $root) {
        if (!$filename) {
            return infomsg(false, '压缩文件名不能为空!');
        }
        $zip = new ZipArchive();
        $msg = $zip->open($filename);
        if (true !== $msg) {
            $zip->close();
            return infomsg(false, $this->getErrorMessage($msg));
        }
        $zip->extractTo($root);
        $zip->close();
        return infomsg(true, 'success');
    }

    /**
     * 压缩之
     * @param string $filename
     * @param array $include
     * @param array $exclude
     * @param string $trimpath
     * @param string $comment
     * @return array
     * @version 1.0 <2015-10-9> SoChishun Added.
     */
    function zip($filename, $include = array(), $exclude = array(), $trimpath = '', $comment = 'default') {
        if (!$filename) {
            return infomsg(false, '压缩文件名不能为空!');
        }
        if (!$include) {
            return infomsg(false, '压缩内容不能为空!');
        }
        if ('default' == $comment) {
            $comment = basename($filename) . PHP_EOL . 'Generate at ' . date('Y-m-d H:i:s') . PHP_EOL . 'Powerd by ExplorerLite.'; // 注释内容
        }
        try {
            $zip = new ZipArchive();
            $msg = $zip->open($filename, ZIPARCHIVE::CREATE | ZIPARCHIVE::OVERWRITE);
            if (true !== $msg) {
                $zip->close();
                return infomsg(false, $this->getErrorMessage($msg));
            }
            if ($comment) {
                $zip->setArchiveComment($comment);
            }
            if ($trimpath) {
                $trimpath = rtrim($trimpath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
            }
            $substart = strlen($trimpath);
            foreach ($include as $source) {
                $this->zip_dir($zip, $source, $exclude, $substart);
            }
            $zip->close();
            return infomsg(true, 'success');
        } catch (Exception $ex) {
            return infomsg(false, $ex->getMessage());
        }
    }

    /**
     * 递归压缩整个目录
     * @param ZipArchive $zip
     * @param string $source 包含的路径
     * @param array $exclude 排除的路径
     * @param int $substart 开始截取的路径字符串(用于去除路径中的根目录路径)
     * @version 1.0 <2015-8-28> SoChishun Added.
     */
    function zip_dir(&$zip, $source, $exclude, $substart = 0) {
        if (is_dir($source)) {
            $source = rtrim($source, DIRECTORY_SEPARATOR);
            if ($handle = opendir($source)) {
                while (false !== ( $f = readdir($handle) )) {
                    if ('.' == $f || '..' == $f) {
                        continue;
                    }
                    $filename = $source . DIRECTORY_SEPARATOR . $f;
                    if (is_dir($filename)) {
                        if ($exclude && in_array($filename, $exclude)) {
                            continue;
                        }
                        $this->zip_dir($zip, $filename, $exclude, $substart);
                    } else {
                        if ($exclude && in_array($filename, $exclude)) {
                            continue;
                        }
                        $zip->addFile($filename, substr($filename, $substart));
                    }
                }
                closedir($handle);
            }
        } else {
            if ($exclude && in_array($source, $exclude)) {
                return;
            }
            $zip->addFile($source);
        }
    }

    /**
     * 返回文本错误信息
     * @param type $code
     * @return string
     * @version 1.0 2018-1-13 by http://php.net/manual/en/ziparchive.open.php
     */
    public function getErrorMessage($code) {
        switch ($code) {
            case 0:
                return 'No error';
            case 1:
                return 'Multi-disk zip archives not supported';
            case 2:
                return 'Renaming temporary file failed';
            case 3:
                return 'Closing zip archive failed';
            case 4:
                return 'Seek error';
            case 5:
                return 'Read error';
            case 6:
                return 'Write error';
            case 7:
                return 'CRC error';
            case 8:
                return 'Containing zip archive was closed';
            case 9:
                return 'No such file';
            case 10:
                return 'File already exists';
            case 11:
                return 'Can\'t open file';
            case 12:
                return 'Failure to create temporary file';
            case 13:
                return 'Zlib error';
            case 14:
                return 'Malloc failure';
            case 15:
                return 'Entry has been changed';
            case 16:
                return 'Compression method not supported';
            case 17:
                return 'Premature EOF';
            case 18:
                return 'Invalid argument';
            case 19:
                return 'Not a zip archive';
            case 20:
                return 'Internal error';
            case 21:
                return 'Zip archive inconsistent';
            case 22:
                return 'Can\'t remove file';
            case 23:
                return 'Entry has been deleted';
            default:
                return 'An unknown error has occurred(' . intval($code) . ')';
        }
    }

}

/**
 * URL重定向
 * @param string $url 重定向的URL地址
 * @param integer $time 重定向的等待时间（秒）
 * @param string $msg 重定向前的提示信息
 * @return void
 * @version 1.0 <2015-10-7> from ThinkPHP
 */
function redirect($url, $time = 0, $msg = '') {
    //多行URL地址支持
    $url = str_replace(array("\n", "\r"), '', $url);
    if (empty($msg))
        $msg = "系统将在{$time}秒之后自动跳转到{$url}！";
    if (!headers_sent()) {
        // redirect
        if (0 === $time) {
            header('Location: ' . $url);
        } else {
            header("refresh:{$time};url={$url}");
            echo($msg);
        }
        exit();
    } else {
        $str = "<meta http-equiv='Refresh' content='{$time};URL={$url}'>";
        if ($time != 0)
            $str .= $msg;
        exit($str);
    }
}

/**
 * 获取文件扩展名类型
 * @param string $exten 扩展名(不带.)
 * @return string
 * @version 1.0 <2015-10-9> SoChishun Added.
 */
function get_exten_catetory($exten) {
    if ($exten) {
        $filetypes = array(
            'zip' => array('zip', 'rar', '7-zip', 'tar', 'gz', 'gzip'),
            'doc' => array('txt', 'rtf', 'doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'wps', 'et'),
            'script' => array('php', 'js', 'css', 'c'),
            'image' => array('jpg', 'jpeg', 'png', 'gif', 'tiff', 'psd', 'bmp', 'ico')
        );
        foreach ($filetypes as $catetory => $extens) {
            if (in_array($exten, $extens)) {
                return $catetory;
            }
        }
    }
    return '';
}

/**
 * 绝对路径转相对路径
 * @param string $path
 * @return string
 * @version 1.0 <2015-10-9> SoChishun Added.
 */
function path_ator($path) {
    $root = pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME);
    $path = substr($path, strlen($root));
    if ('/' != DIRECTORY_SEPARATOR) {
        $path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
    }
    return $path;
}

/**
 * 相对路径转绝对文件路径
 * @param string $path
 * @return string
 * @version 1.0 <2015-10-9> SoChishun Added.
 */
function path_rtoa($path) {
    $root = pathinfo($_SERVER['SCRIPT_FILENAME'], PATHINFO_DIRNAME);
    $pathstr = $root;
    $patharr = explode('/', $path);
    foreach ($patharr as $pathtmpstr) {
        if (!$pathtmpstr || $pathtmpstr == '.') {
            continue;
        }
        if ($pathtmpstr == '..') {
            $pathstr = pathinfo($pathstr, PATHINFO_DIRNAME);
            continue;
        }
        $pathstr .= '/' . $pathtmpstr;
    }

    return $pathstr . (is_dir($path) ? '/' : '');
}
